package proxy;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Constructor;
import java.lang.reflect.ReflectPermission;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import sun.misc.ProxyGenerator;
import sun.misc.VM;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import sun.reflect.misc.ReflectUtil;
import sun.security.util.SecurityConstants;

public class Proxy1 implements java.io.Serializable {

	private static final long serialVersionUID = -2222568056686623797L;
	private static final Class<?>[] constructorParams = { InvocationHandler1.class };
	private static final WeakCache1<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache1<>( new ProxyClassFactory());
	protected InvocationHandler1 h;
	private Proxy1() {}

	protected Proxy1(InvocationHandler1 h) {
		Objects.requireNonNull(h);
		this.h = h;
	}

	//一个利用给定的类加载器和接口类数组生成，定义并返回代理类对象的工厂方法。代理类生成工厂。
	private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
		private static final String proxyClassNamePrefix = "$Proxy1";
		private static final AtomicLong nextUniqueNumber = new AtomicLong();
		@Override
		public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
			String proxyPkg = null; // 代理类的包名
			int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//生成代理类的访问标志, 默认是public final的
			//验证所有非公共代理接口都在同一个包中
			for (Class<?> intf : interfaces) {
				int flags = intf.getModifiers();//获取接口的访问标志
				//如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
				if (!Modifier.isPublic(flags)) {
					accessFlags = Modifier.FINAL;//生成的代理类的访问标志设置为final
					String name = intf.getName();//获取接口全限定名, 例如：java.util.Collection
					int n = name.lastIndexOf('.');
					String pkg = ((n == -1) ? "" : name.substring(0, n + 1));//剪裁后得到包名:java.util
					if (proxyPkg == null) {//生成的代理类的包名和接口包名是一样的
						proxyPkg = pkg;
					} else if (!pkg.equals(proxyPkg)) {//代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
						throw new IllegalArgumentException("non-public interfaces from different packages");
					}
				}
			}
			//如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下：com.sun.proxy
			if (proxyPkg == null) {
				proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
			}
			long num = nextUniqueNumber.getAndIncrement();
			String proxyName = proxyPkg + proxyClassNamePrefix + num;
			byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);//com.sun.proxy.$Proxy10，[interface proxy.People]，
			try {// 返回代理类对象，根据二进制文件生成相应的Class实例。
				return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
			} catch (Exception  e) {
				System.out.println(e.toString());
			}
			return null;
		}
	}

	public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler1 h)
			throws IllegalArgumentException {
		final Class<?>[] intfs = interfaces.clone();//[interface proxy.People]， 
		Class<?> cl =  proxyClassCache.get(loader, intfs);//先走WeakCache，再通过Factory的get方法，最后通过ProxyClassFactory的apply()获取代理类的Class对象。

		try {
			// 从代理类对象中查找参数为InvocationHandler的构造器，获取参数类型是InvocationHandler.class的代理类构造器
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			final InvocationHandler1 ih = h;
			// 检测构造器是否是Public修饰，如果不是则强行转换为可以访问的。
			if (!Modifier.isPublic(cl.getModifiers())) {
				AccessController.doPrivileged(new PrivilegedAction<Void>() {
					public Void run() {
						cons.setAccessible(true);
						return null;
					}
				});
			}
			// 通过反射，将h作为参数，实例化代理类，返回代理类实例。2.利用反射技术实例化代理类，并返回实例化对象。传入InvocationHandler实例去
			//构造一个代理类的实例，所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入，
			return cons.newInstance(new Object[] { h });
		} catch (Exception e) {
			throw new InternalError(e.toString(), e);
		} 
	}

	private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
}
